package com.hzlj.position.job.job;

import cn.hutool.core.thread.ThreadUtil;
import cn.hutool.core.util.ObjectUtil;
import com.fz.common.base.core.RequestUtil;
import com.fz.common.dict.annotation.DictConvert;
import com.fz.common.dict.dto.DictionaryMenuDTO;
import com.fz.common.dict.service.DictionaryService;
import com.google.common.collect.Maps;
import com.hzlj.position.config.common.dto.common.LocateTimerTask;
import com.hzlj.position.config.common.dto.common.PositionAddDTO;
import com.hzlj.position.config.common.dto.jzJbxxDeviceBind.JzJbxxDeviceBindQueryResultDTO;
import com.hzlj.position.config.common.dto.jzJbxxDeviceBind.JzJbxxDeviceBindSimpleQueryParamDTO;
import com.hzlj.position.config.common.dto.operatorConfig.OperatorConfigQueryResultDTO;
import com.hzlj.position.config.config.PositionConfig;
import com.hzlj.position.config.service.JzJbxxDeviceBindService;
import com.hzlj.position.config.service.OperatorConfigService;
import com.hzlj.position.locate.common.dto.position.PositionAddResultDTO;
import com.hzlj.position.locate.common.dto.reportInspection.ReportInspectionExtDTO;
import com.hzlj.position.locate.differentiation.sysCheck.powerOff.PowerOffSysCheck;
import com.hzlj.position.locate.service.DifferentiationDataService;
import com.hzlj.position.locate.service.PositionAddTaskService;
import com.hzlj.position.locate.service.PositionNewestService;
import com.hzlj.position.locate.service.ReportInspectionSyncService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Consumer;

/**
 * 获取位置的定时作业
 **/
@Slf4j
@Component
public class OperatorTimerTaskJob implements LocateTimerTaskJob {
    //是否需要重新定位
    private final Map<String, ReLocateCount> reLocateSwitch = Maps.newConcurrentMap();
    @Resource
    private JzJbxxDeviceBindService deviceBindService;
    @Resource
    private PositionAddTaskService positionAddTaskService;
    @Resource
    private PositionConfig positionConfig;
    @Resource
    private OperatorConfigService operatorConfigService;
    @Resource
    private DictionaryService dictionaryService;
    @Resource
    private PositionNewestService positionNewestService;
    @Resource
    private ReportInspectionSyncService reportInspectionSyncService;
    @Resource
    private PowerOffSysCheck powerOffSysCheck;
    @Resource
    private DifferentiationDataService differentiationDataService;
    /**
     * 获取定位数据
     * 并且有是否需要重新定位的逻辑
     */
    private final Consumer<LocateTimerTask.Task> positionRequest = (task) -> {
        LocateTimerTask.Task.Key taskKey = task.getKey();
        PositionAddDTO position = fetchPosition(taskKey);
        if (position == null) {
            return;
        }
        PowerOffSysCheck.Retry retry = this.powerOffSysCheck.retry(task, position);
        //是否需要重试
        if (retry.isRetry()) {
            addTask(taskKey.getJgCode(),
                    taskKey.getJzId(),
                    taskKey.getDeviceCode(),
                    taskKey.getChannel(),
                    task.getLocateInterval(),
                    retry.getMinutes() * 60,
                    false);
        }

        OperatorConfigService.OperatorTaskConfig operatorTaskConfig = operatorConfigService.taskConfig(taskKey.getJgCode(), taskKey.getChannel());
        ReLocateCount reLocated = reLocateSwitch.computeIfAbsent(task.key(), (s) -> new ReLocateCount(positionConfig.getReLocate().getCount()));
        if (operatorTaskConfig.getConfig().getExt().reLocate(position.getOperatorReturnCode())) {
            //需要重新发起定位
            if (reLocated.incr()) {
                log.debug("定位失败，{}秒后重新定位:taskKey={},{}", positionConfig.getReLocate().getSeconds(), taskKey, position);
                addTask(taskKey.getJgCode(),
                        taskKey.getJzId(),
                        taskKey.getDeviceCode(),
                        taskKey.getChannel(),
                        task.getLocateInterval(),
                        positionConfig.getReLocate().getSeconds(),
                        false);
                return;
            }
            //已经重新定位，需要发送通知
            if (positionConfig.getReLocate().getInspection()
                    && operatorTaskConfig.getConfig().getExt().inspection(position.getOperatorReturnCode())) {
                log.info("定位失败，经过{}次重试，需要发起核查点验:taskKey={},{}", reLocated.totalCount, taskKey, position);
                reportInspectionSyncService.syncReportInspectionPositionCheck(
                        taskKey.getJzId(),
                        taskKey.getJgCode(),
                        new ReportInspectionExtDTO(position.getId(), position.getPositionTime()));
            }
        }
        reLocated.init();
        positionAddTaskService.add(position);
    };

    /**
     * 实时获取定位
     */
    @DictConvert
    public PositionAddResultDTO fetchPosition(String jgCode,
                                              String jzId,
                                              String deviceCode,
                                              String channel) {
        PositionAddDTO position = fetchPosition(new LocateTimerTask.Task.Key(jgCode, jzId, deviceCode, channel));
        if (ObjectUtil.isNull(position)) {
            return PositionAddResultDTO.fail("定位数据为空");
        }
        //添加到队列中
        position.setReturnPosition(true);
        return positionAddTaskService.add(position);
    }

    public void cancelTask(String jgCode,
                           String jzId,
                           String deviceCode,
                           String channel) {
        LocateTimerTask.Task task = new LocateTimerTask.Task(
                new LocateTimerTask.Task.Key(jgCode, jzId, deviceCode, channel)
        );
        LocateTimerTask.cancel(task);
    }

    public void addTask(String jgCode,
                        String jzId,
                        String deviceCode,
                        String channel,
                        Integer interval) {
        addTask(jgCode, jzId, deviceCode, channel, interval, 0, false);
    }

    public void addTask(String jgCode,
                        String jzId,
                        String deviceCode,
                        String channel,
                        Integer interval,
                        Integer delay,
                        boolean addIfAbsent) {
        LocateTimerTask.Task task = new LocateTimerTask.Task(
                new LocateTimerTask.Task.Key(jgCode, jzId, deviceCode, channel),
                interval,
                delay
        );
        OperatorConfigService.OperatorTaskConfig operatorTaskConfig = operatorConfigService.taskConfig(jgCode, channel);
        if (operatorTaskConfig == null) {
            return;
        }
        if (addIfAbsent) {
            LocateTimerTask.addIfAbsent(task, operatorTaskConfig.getTaskQueue());
        } else {
            LocateTimerTask.add(task, operatorTaskConfig.getTaskQueue());
        }
    }


    @PostConstruct
    private void start() {
        //立即执行
        ThreadUtil.execAsync(() -> start(false));
        //每30分钟跑一次
        ScheduledExecutorService scheduled = Executors.newScheduledThreadPool(1);
        scheduled.scheduleAtFixedRate(
                () -> start(true),
                30,
                30,
                TimeUnit.MINUTES
        );
    }

    private void start(boolean addIfAbsent) {
        List<String> channels = operatorConfigService.channels();
        List<OperatorConfigService.OperatorTaskConfig> operatorTaskConfigs = operatorConfigService.taskConfigs();
        if (!positionConfig.isOperatorTask() || ObjectUtil.isEmpty(channels)) {
            log.info("不用开启实时运营商定位作业");
            return;
        }
        //启动作业
        LocateTimerTask.start(new ArrayList<>(operatorTaskConfigs), positionRequest);
        //获取所有的绑定数据
        List<JzJbxxDeviceBindQueryResultDTO> devices = deviceBindService.listJzJbxxDeviceBind(
                new JzJbxxDeviceBindSimpleQueryParamDTO().setChannels(channels).setMdy(false)
        );
        log.info("开启运营商定位任务,size={}", devices.size());
        //添加到监控队列中
        for (JzJbxxDeviceBindQueryResultDTO device : devices) {
            try {
                int interval = device.getLocateInterval();
                int delay = getLocateDelay(positionNewestService, differentiationDataService, device);
                addTask(device.getJgCode(), device.getJzId(), device.getDeviceCode(), device.getChannel(), interval, delay, addIfAbsent);
            } catch (Exception e) {
                log.error("开启运营商定位任务失败,device={}", device, e);
            }
        }
    }

    @PreDestroy
    private void stop() {
        LocateTimerTask.stop();
    }

    private PositionAddDTO fetchPosition(LocateTimerTask.Task.Key key) {
        //获取位置
        try {
            OperatorConfigService.OperatorTaskConfig operatorTaskConfig = operatorConfigService.taskConfig(key.getJgCode(), key.getChannel());
            if (operatorTaskConfig == null) {
                return null;
            }
            OperatorConfigQueryResultDTO config = operatorTaskConfig.getConfig();
            return operatorTaskConfig.getOperatorLocateRest().fetchPosition(
                    config,
                    key.getJgCode(),
                    key.getDeviceCode(),
                    (type, code) -> {
                        DictionaryMenuDTO dict = dictionaryService.getByTypeCode(RequestUtil.getAppId(), type, code);
                        return dict == null ? null : dict.getName();
                    }
            );
        } catch (Throwable e) {
            log.error("运营商定位异常:{}->{}", key.getDeviceCode(), key.getChannel(), e);
        }
        return null;
    }

    private static class ReLocateCount {
        private final Integer totalCount;
        private final AtomicInteger count = new AtomicInteger(0);

        public ReLocateCount(Integer totalCount) {
            this.totalCount = totalCount;
        }

        /**
         * 返回是否可以继续重试
         */
        private boolean incr() {
            return count.getAndIncrement() < totalCount;
        }

        private void init() {
            count.set(0);
        }
    }
}
